//--------------------------------------------------------------------------------
// This file is a portion of the PracticeEngine Rendering Engine.  It is distributed
// under the MIT License, available in the root of this distribution and 
// at the following URL:
//
// http://www.opensource.org/licenses/mit-license.php
//
// Copyright (c) Ferdinand Li 
//--------------------------------------------------------------------------------
#ifndef PEMath_h__
#define PEMath_h__

namespace PracEng
{
#define PracEng_PI				  (3.141592654f)
#define PracEng_2PI				  (6.283185307f)
#define PracEng_1DIV2PI           (0.159154943f)
#define PracEng_PIDIV2            (1.570796327f)

	class PEVector4;
	class PEVector3
	{
	public:
		PEVector3();
		PEVector3(float Val);
		PEVector3(float X, float Y, float Z);
		PEVector3(const PEVector3& Vector);

		PEVector3 Cross(const PEVector3& RefVector) const;
		float Dot(const PEVector3& RefVector) const;
		void Normalize();
		float LengthSquared() const;
		float Length() const;

		static float CalcAngleBetween(const PEVector3& A, const PEVector3& B);
		static PEVector3 Cross(const PEVector3& A, const PEVector3& B);
		static float Dot(const PEVector3& A, const PEVector3& B);
		static PEVector3 Normalize(const PEVector3& A);

		bool IsZero(){ return x == 0.0f && y == 0.0f && z == 0.0f; }

		PEVector3& operator= (const PEVector3& Vector);
		PEVector3& operator= (const PEVector4& Vector);

		float operator[] (int iPos) const;
		float& operator[] (int iPos);

		bool operator== (const PEVector3& Vector) const;
		bool operator!= (const PEVector3& Vector) const;

		PEVector3 operator+ (const PEVector3& Vector) const;
		PEVector3 operator- (const PEVector3& Vector) const;
		PEVector3 operator* (const PEVector3& Vector) const;
		PEVector3 operator* (float fScalar) const;
		PEVector3 operator/ (const PEVector3& Vector) const;
		PEVector3 operator/ (float fScalar) const;
		PEVector3 operator- () const;

		PEVector3& operator+= (const PEVector3& Vector);
		PEVector3& operator-= (const PEVector3& Vector);
		PEVector3& operator*= (const PEVector3& Vector);
		PEVector3& operator*= (float fScalar);
		PEVector3& operator/= (const PEVector3& Vector);
		PEVector3& operator/= (float fScalar);

	public:
		float x;
		float y;
		float z;
	};

	class PEVector4
	{
	public:
		PEVector4();
		PEVector4(float X, float Y, float Z, float W);
		PEVector4(const PEVector4& ref);
		PEVector4(const PEVector3& ref, float W = 1.0f);

		float Dot(PEVector4& vector);

		PEVector3 Vec3();

		PEVector4& operator= (const PEVector4& Vector);
		float operator[] (int iPos) const;
		float& operator[] (int iPos);

		bool operator== (const PEVector4& Vector) const;
		bool operator!= (const PEVector4& Vector) const;

		PEVector4 operator+ (const PEVector4& Vector) const;
		PEVector4 operator- (const PEVector4& Vector) const;
		PEVector4 operator* (float fScalar) const;
		PEVector4 operator* (const PEVector4& Vector) const;
		PEVector4 operator/ (float fScalar) const;
		PEVector4 operator/ (const PEVector4& Vector) const;

		PEVector4& operator+= (const PEVector4& Vector);
		PEVector4& operator-= (const PEVector4& Vector);
		PEVector4& operator*= (float fScalar);
		PEVector4& operator*= (const PEVector4& Vector);
		PEVector4& operator/= (float fScalar);
		PEVector4& operator/= (const PEVector4& Vector);


	public:
		float x;
		float y;
		float z;
		float w;

	};

	class PEMatrix3x3
	{
	public:
		union
		{
			struct
			{
				float _11, _12, _13;
				float _21, _22, _23;
				float _31, _32, _33;
			};
			float m[3][3];
		};

		PEMatrix3x3();
		PEMatrix3x3(const PEMatrix3x3& M);
		PEMatrix3x3(const PEVector3& Row1, const PEVector3& Row2, const PEVector3& Row3);

		static PEMatrix3x3 GetIdentityMatrix();
		void RotationX(float fRadians);
		void RotationY(float fRadians);
		void RotationZ(float fRadians);
		void Rotation(PEVector3& Rot);
		PEMatrix3x3& operator= (const PEMatrix3x3& M);
		float operator() (int iRow, int iCol) const;
		float& operator() (int iRow, int iCol);
	
		bool operator== (const PEMatrix3x3& Matrix) const;
		bool operator!= (const PEMatrix3x3& Matrix) const;
		PEMatrix3x3 operator+ (const PEMatrix3x3& Matrix) const;
		PEMatrix3x3 operator- (const PEMatrix3x3& Matrix) const;
		PEMatrix3x3 operator* (const PEMatrix3x3& Matrix) const;
		PEMatrix3x3 operator* (float fScalar) const;
		PEMatrix3x3 operator/ (float fScalar) const;
		PEMatrix3x3 operator- () const;
		PEMatrix3x3& operator+= (const PEMatrix3x3& Matrix);
		PEMatrix3x3& operator-= (const PEMatrix3x3& Matrix);
		PEMatrix3x3& operator*= (const PEMatrix3x3& Matrix);
		PEMatrix3x3& operator*= (float fScalar);
		PEMatrix3x3& operator/= (float fScalar);
		PEVector3 operator* (const PEVector3& Vector) const;

	};

	class PEMatrix
	{
	public:
		union
		{
			struct {
				float        _11, _12, _13, _14;
				float        _21, _22, _23, _24;
				float        _31, _32, _33, _34;
				float        _41, _42, _43, _44;

			};
			float m[4][4];
		};

		PEMatrix();
		PEMatrix(const PEVector4& Row1, const PEVector4& Row2, const PEVector4& Row3, const PEVector4& Row4);
		PEMatrix& operator =(const PEMatrix& M);
		PEMatrix operator - () const;
		
		PEMatrix& operator += (const PEMatrix& M);
		PEMatrix& operator -= (const PEMatrix& M);
		PEMatrix& operator *= (const PEMatrix& M);
		PEMatrix& operator *= (float fScalar);
		PEMatrix& operator /= (float fScalar);

		PEVector4 operator* (const PEVector4& V) const;
		PEVector3 operator* (PEVector3 V) const;

		PEMatrix operator+ (const PEMatrix& Matrix) const;
		PEMatrix operator- (const PEMatrix& Matrix) const;
		PEMatrix operator* (const PEMatrix& M) const;
		PEMatrix operator* (float fScalar) const;
		PEMatrix operator/ (float fScalar) const;

		PEVector3 GetBasisX() const;
		PEVector3 GetBasisY() const;
		PEVector3 GetBasisZ() const;

		void RotationY(float fRadians);

		void SetTranslation(const PEVector3& Trans);
		void SetTranslation(const float& x, const float& y, const float& z);
		PEVector3 GetTranslation() const;
		PEMatrix3x3 GetRotation() const;
		void Transpose();

		void Scale(float xScale, float yScale, float zScale);

		/*Make a left hand Perspective Matrix
		fFOV: the radians of verticle field of view
		fAspect: width / height
		fNearPlane: near view plane
		fFarPlane: far view plane
		*/
		static PEMatrix MakePerspectiveMatrix(float fFOV, float fAspect, float fNearPlane, float fFarPlane);

		static PEMatrix GetIdentityMatrix();

		static PEMatrix MakeTranspose(const PEMatrix& M);
		
		static PEMatrix MakeInverse(const PEMatrix& M);

		static PEMatrix SetRotationYawPitchRoll(const PEVector4& PitchYawRoll);

		static PEMatrix MakeLookAtLHMatrix(PEVector3* pEyePt, PEVector3* pLookatPt, PEVector3* pLookUp);
		
		static PEMatrix MakeOrthoMatrix(float w, float h, float zn, float zf);

	};

	inline void PEScalarSinCos(float& pSin, float& pCos, float Value)
	{
		float quotient = PracEng_1DIV2PI*Value;
		if (Value >= 0.0f)
		{
			quotient = (float)((int)(quotient + 0.5f));
		}
		else
		{
			quotient = (float)((int)(quotient - 0.5f));
		}
		float y = Value - PracEng_2PI*quotient;

		float sign;
		if (y > PracEng_PIDIV2)
		{
			y = PracEng_PI - y;
			sign = -1.0f;
		}
		else if (y < -PracEng_PIDIV2)
		{
			y = -PracEng_PI - y;
			sign = -1.0f;
		}
		else
		{
			sign = +1.0f;
		}

		float y2 = y * y;

		// 11-degree minimax approximation
		pSin = (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y;

		// 10-degree minimax approximation
		float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f;
		pCos = sign*p;
	}
	
	inline void PEVectorSinCos(PEVector4& pSin, PEVector4& pCos, PEVector4& Value)
	{
		PEScalarSinCos(pSin.x, pCos.x, Value.x);
		PEScalarSinCos(pSin.y, pCos.y, Value.y);
		PEScalarSinCos(pSin.z, pCos.z, Value.z);
		PEScalarSinCos(pSin.w, pCos.w, Value.w);
	}

	inline PEVector4 PEQuatRotPitchYawFromVector(const PEVector4& Angles)
	{
		static const PEVector4 Sign(1.0f, -1.0f, -1.0f, 1.0f);

		PEVector4 HalfAngles = Angles * 0.5f;
		PEVector4 SinAngles, CosAngles;
		PEVectorSinCos(SinAngles, CosAngles, HalfAngles);

		PEVector4 P0(SinAngles.x, CosAngles.x, CosAngles.x, CosAngles.x);
		PEVector4 Y0(CosAngles.y, SinAngles.y, CosAngles.y, CosAngles.y);
		PEVector4 R0(CosAngles.z, CosAngles.z, SinAngles.z, CosAngles.z);
		PEVector4 P1(CosAngles.x, SinAngles.x, SinAngles.x, SinAngles.x);
		PEVector4 Y1(SinAngles.y, CosAngles.y, SinAngles.y, SinAngles.y);
		PEVector4 R1(SinAngles.z, SinAngles.z, CosAngles.z, SinAngles.z);

		PEVector4 Q1 = P1 * Sign;
		PEVector4 Q0 = P0 * Y0;
		Q1 = Q1 * Y1;
		Q0 = Q0 * R0;
		PEVector4 Q = Q1*R1 + Q0;
		return Q;
	}

	inline PEMatrix PEMatrixRotQuat(PEVector4& Quaternion)
	{
		static const PEVector4 Constant1110(1.0f, 1.0f, 1.0f, 0.0f);

		PEVector4 Q0 = Quaternion + Quaternion;
		PEVector4 Q1 = Quaternion * Q0;
		PEVector4 V0(Q1.y, Q1.x, Q1.x, Constant1110.w);
		PEVector4 V1(Q1.z, Q1.z, Q1.y, Constant1110.w);
		PEVector4 R0 = Constant1110 - V0;
		R0 = R0 - V1;
		V0.x = Quaternion.x; V0.y = Quaternion.x, V0.z = Quaternion.y, V0.w = Quaternion.w;
		V1.x = Q0.z, V1.y = Q0.y, V1.z = Q0.z, V1.w = Q0.w;
		V0 = V0 * V1;

		V1.x = V1.y = V1.z = V1.w;
		PEVector4 V2(Q0.y, Q0.z, Q0.x, Q0.w);
		V1 = V1 * V2;

		PEVector4 R1 = V0 + V1;
		PEVector4 R2 = V0 - V1;

		V0.x = R1.y, V0.y = R2.x, V0.z = R2.y, V0.w = R1.z;
		V1.x = R1.x, V1.y = R2.z, V1.z = R1.x, V1.w = R2.z;

		PEMatrix M;
		M._11 = R0.x, M._12 = V0.x, M._13 = V0.y, M._14 = R0.w;
		M._21 = V0.z, M._22 = R0.y, M._23 = V0.w, M._24 = R0.w;
		M._31 = V1.x, M._32 = V1.y, M._33 = R0.z, M._34 = R0.w;
		M._41 = .0f, M._42 = .0f, M._43 = .0f, M._44 = 1.f;

		return M;
	}

	struct PEFloat3
	{
		union
		{
			struct
			{
				float X;
				float Y;
				float Z;
			};
			float f[3];
		};

		PEFloat3() { X = Y = Z = 0.0f; }
		PEFloat3(const PEFloat3& ref) :X(ref.X), Y(ref.Y), Z(ref.Z){}
		PEFloat3(const PEVector3& ref) :X(ref.x), Y(ref.y), Z(ref.z){}
	};
	struct PEFloat2
	{
		union
		{
			struct
			{
				float X;
				float Y;
			};
			float f[2];
		};

		PEFloat2() { X = Y = 0.0f; }
		PEFloat2(float x, float y) :X(x), Y(y){}
		PEFloat2(const PEFloat2& ref) :X(ref.X), Y(ref.Y){}
		PEFloat2(const PEFloat3& ref) :X(ref.X), Y(ref.Y){}
	};

	struct PEFloat4
	{
		union
		{
			struct
			{
				float X;
				float Y;
				float Z;
				float W;
			};
			float f[4];
		};

		PEFloat4() { X = Y = Z = W = .0f; }
		PEFloat4(const PEFloat3& ref) :X(ref.X), Y(ref.Y), Z(ref.Z), W(.0f){}
		PEFloat4(const PEFloat4& ref) :X(ref.X), Y(ref.Y), Z(ref.Z), W(ref.W){}
		PEFloat4(const PEVector3& ref) :X(ref.x), Y(ref.y), Z(ref.z), W(.0f){}
		PEFloat4(const PEVector4& ref) :X(ref.x), Y(ref.y), Z(ref.z), W(ref.w){}
	};

}

#endif // PEMath_h__
